using System;
using System.Collections.Generic;
using System.Text;
using System.Diagnostics;

namespace BigNumber
{
  public class Int64BigNumber : IIntegerBigNumber, IConvertible, IComparable<Int64BigNumber>
  {
    Int64 m_InnerNumber;

    #region Constructors
    public Int64BigNumber(string number)
    {
      m_InnerNumber = Convert.ToInt64(number);
    }

    public Int64BigNumber()
    {
      m_InnerNumber = 0;
    }

    private Int64BigNumber(Int32 number)
    {
      m_InnerNumber = Convert.ToInt64(number);
    }
    private Int64BigNumber(Int64 number)
    {
      m_InnerNumber = number;
    }

    private Int64BigNumber(Int64BigNumber number)
    {
      if (number == null) m_InnerNumber = 0;
      else m_InnerNumber = number.m_InnerNumber;
    }
    #endregion

    #region Helper Functions
    private void SetNegative(bool isNegative)
    {
       m_InnerNumber = System.Math.Abs(m_InnerNumber);
      if (isNegative)
        m_InnerNumber = -m_InnerNumber;
    }

    private void SetDigit(int index, byte digit)
    {
      int currentDigit = GetDigit(index);
      Int64 unitValue = 1;
      for (int i = 0; i < index; i++)
        unitValue *= 10;
      Int64 val = unitValue * Convert.ToInt64(currentDigit);
      m_InnerNumber -= val;
      val = unitValue * Convert.ToInt64(digit);
      m_InnerNumber += val;
    }
    #endregion

    #region IBigNumber Members

    public void Initialize(string number)
    {
      m_InnerNumber = Convert.ToInt64(number);
    }

    public void Initialize(StdBigNumber number)
    {
      Int64 multiplier = 1;
      m_InnerNumber = 0;
      for (int i = 0; i < number.DigitCount; i++, multiplier *= 10)
        m_InnerNumber += multiplier * Convert.ToInt64(number.GetDigit(i));
      SetNegative(number.IsNegative);
    }

    public int GetDigit(int index)
    {
      Int64 tempNumber = System.Math.Abs(m_InnerNumber);
      for (int i = 0; i < index; i++)
      {
        tempNumber = tempNumber / 10;
      }
      return Convert.ToInt32(tempNumber % 10);
    }

    public void Negate()
    {
      m_InnerNumber *= -1;
    }

    public void AddOne()
    {
      m_InnerNumber++;
    }

    public void Shift(int n)
    {
      for (int i = 0; i < n; i++)
        m_InnerNumber *= 10;
    }

    public bool IsNegative
    {
      get
      {
        return m_InnerNumber < 0;
      }
    }

    public int DigitCount
    {
      get 
      {
        if (m_InnerNumber == 0) return 1;
        Int64 temp = System.Math.Abs(m_InnerNumber);
        int counter = 0;
        while (temp > 0)
        {
          counter++;
          temp = temp / 10;
        }
        return counter;
      }
    }

    public bool IsZero
    {
      get { return m_InnerNumber == 0; }
    }
    
    public bool IsOne
    {
      get
      {
        return m_InnerNumber == 1 || m_InnerNumber == -1;
      }
    }

    #endregion

    #region Overrides
    public override string ToString()
    {
      return m_InnerNumber.ToString();
    }
    #endregion

    #region Math Operators
    private static Int64BigNumber Add(Int64BigNumber firstNumber, Int64BigNumber secondNumber)
    {
      return new Int64BigNumber(firstNumber.m_InnerNumber + secondNumber.m_InnerNumber);
    }

    public static Int64BigNumber operator +(Int64BigNumber firstNumber, Int64BigNumber secondNumber)
    {
      if (ReferenceEquals(firstNumber,null)) throw new ArgumentNullException("firstNumber");
      if (ReferenceEquals(secondNumber ,null)) throw new ArgumentNullException("secondNumber");
      return Add(firstNumber, secondNumber);
    }

    private static Int64BigNumber Subtract(Int64BigNumber firstNumber, Int64BigNumber secondNumber)
    {
      return new Int64BigNumber(firstNumber.m_InnerNumber - secondNumber.m_InnerNumber);
    }

    public static Int64BigNumber operator -(Int64BigNumber firstNumber, Int64BigNumber secondNumber)
    {
      if (ReferenceEquals(firstNumber,null)) throw new ArgumentNullException("firstNumber");
      if (ReferenceEquals(secondNumber ,null)) throw new ArgumentNullException("secondNumber");
      return Subtract(firstNumber, secondNumber);
    }
    
    private static Int64BigNumber Multiply(Int64BigNumber firstNumber, Int64BigNumber secondNumber)
    {
      return new Int64BigNumber(firstNumber.m_InnerNumber * secondNumber.m_InnerNumber);
    }

    public static Int64BigNumber operator *(Int64BigNumber firstNumber, Int64BigNumber secondNumber)
    {
      if (ReferenceEquals(firstNumber,null)) throw new ArgumentNullException("firstNumber");
      if (ReferenceEquals(secondNumber ,null)) throw new ArgumentNullException("secondNumber");
      return Multiply(firstNumber, secondNumber);
    }

    private static Int64BigNumber Divide(Int64BigNumber firstNumber, Int64BigNumber secondNumber)
    {
      return new Int64BigNumber(firstNumber.m_InnerNumber / secondNumber.m_InnerNumber);
    }

    public static Int64BigNumber operator /(Int64BigNumber firstNumber, Int64BigNumber secondNumber)
    {
      if (ReferenceEquals(firstNumber,null)) throw new ArgumentNullException("firstNumber");
      if (ReferenceEquals(secondNumber ,null)) throw new ArgumentNullException("secondNumber");
      return Divide(firstNumber, secondNumber);
    }

    private static Int64BigNumber Modulus(Int64BigNumber firstNumber, Int64BigNumber secondNumber)
    {
      return new Int64BigNumber(firstNumber.m_InnerNumber % secondNumber.m_InnerNumber);
    }

    public static Int64BigNumber operator %(Int64BigNumber firstNumber, Int64BigNumber secondNumber)
    {
      if (ReferenceEquals(firstNumber,null)) throw new ArgumentNullException("firstNumber");
      if (ReferenceEquals(secondNumber ,null)) throw new ArgumentNullException("secondNumber");
      return Modulus(firstNumber, secondNumber);
    }

    public static Int64BigNumber operator --(Int64BigNumber number)
    {
      if (number == null) throw new ArgumentNullException("number");
      Int64BigNumber result = new Int64BigNumber(number.m_InnerNumber - 1);
      return result;
    }

    public static Int64BigNumber operator ++(Int64BigNumber number)
    {
      if (number == null) throw new ArgumentNullException("number");
      Int64BigNumber result = new Int64BigNumber(number.m_InnerNumber + 1);
      return result;
    }

    public static Int64BigNumber operator -(Int64BigNumber number)
    {
      if (number == null) throw new ArgumentNullException("number");
      Int64BigNumber result = new Int64BigNumber(number);
      result.Negate();
      return result;
    }
    #endregion

    #region Comparison Operators
    public static bool operator <(Int64BigNumber firstNumber, Int64BigNumber secondNumber)
    {
      if (ReferenceEquals(firstNumber,null))  throw new ArgumentNullException("firstNumber");
      if (ReferenceEquals(secondNumber ,null)) throw new ArgumentNullException("secondNumber");
      if (firstNumber.CompareTo(secondNumber) < 0)
        return true;
      return false;
    }

    public static bool operator >(Int64BigNumber firstNumber, Int64BigNumber secondNumber)
    {
      if (ReferenceEquals(firstNumber,null)) throw new ArgumentNullException("firstNumber");
      if (ReferenceEquals(secondNumber ,null)) throw new ArgumentNullException("secondNumber");
      if (firstNumber.CompareTo(secondNumber) > 0)
        return true;
      return false;
    }

    public static bool operator ==(Int64BigNumber firstNumber, Int64BigNumber secondNumber)
    {
      if (ReferenceEquals(firstNumber,null) && ReferenceEquals(secondNumber ,null)) return true;
      if (ReferenceEquals(firstNumber,null)) return false;
      if (ReferenceEquals(secondNumber ,null)) return false;
      if (firstNumber.CompareTo(secondNumber) == 0)
        return true;
      return false;
    }

    public static bool operator !=(Int64BigNumber firstNumber, Int64BigNumber secondNumber)
    {
      if (ReferenceEquals(firstNumber,null) && ReferenceEquals(secondNumber ,null)) return false;
      if (ReferenceEquals(firstNumber,null)) return true;
      if (ReferenceEquals(secondNumber ,null)) return true;

      if (firstNumber.CompareTo(secondNumber) != 0)
        return true;
      return false;
    }

    public static bool operator <=(Int64BigNumber firstNumber, Int64BigNumber secondNumber)
    {
      if (ReferenceEquals(firstNumber,null) && ReferenceEquals(secondNumber ,null)) return true;
      if (ReferenceEquals(firstNumber,null)) return false;
      if (ReferenceEquals(secondNumber ,null)) return false;
      if (firstNumber.CompareTo(secondNumber) <= 0)
        return true;
      return false;
    }

    public static bool operator >=(Int64BigNumber firstNumber, Int64BigNumber secondNumber)
    {
      if (ReferenceEquals(firstNumber,null) && ReferenceEquals(secondNumber ,null)) return true;
      if (ReferenceEquals(firstNumber,null)) return false;
      if (ReferenceEquals(secondNumber ,null)) return false;
      if (firstNumber.CompareTo(secondNumber) >= 0)
        return true;
      return false;
    }

    #endregion

    #region Implicit Convertions
    public static implicit operator int(Int64BigNumber number)
    {
      if (number == null) throw new ArgumentNullException("number");
      checked
      {
        int result;
        if (Int32.TryParse(number.ToString(), out result))
          return result;
        else
          throw new Exception("Unable to convert Int64BigNumber " + number.ToString() + " to integer");
      }
    }

    public static implicit operator Int64BigNumber(int number)
    {
      return new Int64BigNumber(number);
    }

    public static implicit operator Int64BigNumber(string number)
    {
      return new Int64BigNumber(number);
    }
    #endregion

    #region IBigNumber Members


    public IBigNumber IAdd(IBigNumber second)
    {
      if (ReferenceEquals(second  ,null)) throw new ArgumentNullException("second");
      Int64BigNumber sSecond = second as Int64BigNumber;
      return this + sSecond;
    }

    public IBigNumber ISub(IBigNumber second)
    {
      if (ReferenceEquals(second  ,null)) throw new ArgumentNullException("second");
      Int64BigNumber sSecond = second as Int64BigNumber;
      return this - sSecond;
    }

    public IBigNumber IMul(IBigNumber second)
    {
      if (ReferenceEquals(second  ,null)) throw new ArgumentNullException("second");
      Int64BigNumber sSecond = second as Int64BigNumber;
      return this * sSecond;
    }

    public IBigNumber IDiv(IBigNumber second)
    {
      if (ReferenceEquals(second  ,null)) throw new ArgumentNullException("second");
      Int64BigNumber sSecond = second as Int64BigNumber;
      return this / sSecond;

    }

    public IBigNumber IMod(IBigNumber second)
    {
      if (ReferenceEquals(second  ,null)) throw new ArgumentNullException("second");
      Int64BigNumber sSecond = second as Int64BigNumber;
      return this % sSecond;
    }

    public IBigNumber IClone()
    {
      return new Int64BigNumber(this);
    }

    public IBigNumber IConstruct(string number)
    {
      return new Int64BigNumber(number);
    }

    public void IAbs()
    {
      SetNegative(false);
    }

    public bool IEqual(IBigNumber second)
    {
      if (ReferenceEquals(second  ,null)) throw new ArgumentNullException("second");
      return this == (second as Int64BigNumber);
    }

    public bool ISmaller(IBigNumber second)
    {
      if (ReferenceEquals(second  ,null)) throw new ArgumentNullException("second");
      return this.m_InnerNumber < (second as Int64BigNumber).m_InnerNumber;
    }
    #endregion

    #region IConvertible Members

    TypeCode IConvertible.GetTypeCode()
    {
      return TypeCode.Object;
    }

    bool IConvertible.ToBoolean(IFormatProvider provider)
    {
      if (IsZero) return false;
      return true;
    }

    byte IConvertible.ToByte(IFormatProvider provider)
    {
      return Convert.ToByte(m_InnerNumber);
    }

    char IConvertible.ToChar(IFormatProvider provider)
    {
      return Convert.ToChar(m_InnerNumber);
    }

    DateTime IConvertible.ToDateTime(IFormatProvider provider)
    {
      try
      {
        return new DateTime((this as IConvertible).ToInt64(provider));
      }
      catch (InvalidCastException)
      {
        throw new InvalidCastException(String.Format("Cannot convert {0} to DateTime because the values are incompatible", ToString()));
      }
    }

    decimal IConvertible.ToDecimal(IFormatProvider provider)
    {
      try
      {
        checked
        {
          return Convert.ToInt32(ToString());
        }
      }
      catch (System.OverflowException)
      {
        throw new InvalidCastException(String.Format("Cannot convert {0} to Decimal because the values are incompatible", ToString()));
      }
    }

    double IConvertible.ToDouble(IFormatProvider provider)
    {
      try
      {
        checked
        {
          return Convert.ToInt32(ToString());
        }
      }
      catch (System.OverflowException)
      {
        throw new InvalidCastException(String.Format("Cannot convert {0} to Double because the values are incompatible", ToString()));
      }
    }

    short IConvertible.ToInt16(IFormatProvider provider)
    {
      try
      {
        checked
        {
          return Convert.ToInt16(ToString());
        }
      }
      catch (System.OverflowException)
      {
        throw new InvalidCastException(String.Format("Cannot convert {0} to Int16 because the values are incompatible", ToString()));
      }
    }

    int IConvertible.ToInt32(IFormatProvider provider)
    {
      try
      {
        checked
        {
          return Convert.ToInt32(ToString());
        }
      }
      catch (System.OverflowException)
      {
        throw new InvalidCastException(String.Format("Cannot convert {0} to Int32 because the values are incompatible", ToString()));
      }

    }

    long IConvertible.ToInt64(IFormatProvider provider)
    {
      try
      {
        checked
        {
          return Convert.ToInt64(ToString());
        }
      }
      catch (System.OverflowException)
      {
        throw new InvalidCastException(String.Format("Cannot convert {0} to Int64 because the values are incompatible", ToString()));
      }
    }

    sbyte IConvertible.ToSByte(IFormatProvider provider)
    {
      try
      {
        checked
        {
          return Convert.ToSByte(ToString());
        }
      }
      catch (System.OverflowException)
      {
        throw new InvalidCastException(String.Format("Cannot convert {0} to sbyte because the values are incompatible", ToString()));
      }
    }

    float IConvertible.ToSingle(IFormatProvider provider)
    {
      try
      {
        checked
        {
          return Convert.ToSingle(ToString());
        }
      }
      catch (System.OverflowException)
      {
        throw new InvalidCastException(String.Format("Cannot convert {0} to float because the values are incompatible", ToString()));
      }
    }

    string IConvertible.ToString(IFormatProvider provider)
    {
      return ToString();
    }

    object IConvertible.ToType(Type conversionType, IFormatProvider provider)
    {
      return Convert.ChangeType(ToString(), conversionType);
    }

    ushort IConvertible.ToUInt16(IFormatProvider provider)
    {
      try
      {
        checked
        {
          return Convert.ToUInt16(ToString());
        }
      }
      catch (System.OverflowException)
      {
        throw new InvalidCastException(String.Format("Cannot convert {0} to UInt16 because the values are incompatible", ToString()));
      }
    }

    uint IConvertible.ToUInt32(IFormatProvider provider)
    {
      try
      {
        checked
        {
          return Convert.ToUInt32(ToString());
        }
      }
      catch (System.OverflowException)
      {
        throw new InvalidCastException(String.Format("Cannot convert {0} to UInt32 because the values are incompatible", ToString()));
      }
    }

    ulong IConvertible.ToUInt64(IFormatProvider provider)
    {
      try
      {
        checked
        {
          return Convert.ToUInt64(ToString());
        }
      }
      catch (System.OverflowException)
      {
        throw new InvalidCastException(String.Format("Cannot convert {0} to UInt64 because the values are incompatible", ToString()));
      }
    }

    #endregion

    #region IComparable<Int64BigNumber> Members

    public int CompareTo(Int64BigNumber other)
    {
      if (ReferenceEquals(other  ,null)) throw new ArgumentNullException("other");
      return m_InnerNumber.CompareTo(other.m_InnerNumber);
    }

    #endregion
  }
}
